/*
 * Copyright (C) 2011-2014 Felix Fietkau <nbd@openwrt.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */
#define DEBUG

#include "mdb.h"
#include "mtuya.h"
#include "tuya_iot_dp.h"

static struct mtuya_t tuya_context;

#define mty_get()	(&tuya_context)

struct mty_dpattr_t *dpid2dpattr(int32_t dpid)
{
	int i; 
	struct mtuya_t *mty_p = mty_get();
	
	for(i = 0; i < mty_p->mdps_num; i++) {
		struct mty_dpattr_t *dpattr = &mty_p->mdps[i];
		if (dpattr->dp_id == dpid) {
			return dpattr;
		}
	}
	
	return NULL;
}

struct mty_dpattr_t *addr2dpattr(uint32_t addr, uint32_t *offset)
{
	int i; 
	struct mtuya_t *mty_p = mty_get();

	for(i = *offset; i < mty_p->mdps_num; i++) {
		struct mty_dpattr_t *dpattr = &mty_p->mdps[i];
		if (dpattr->db_addr == addr) {
			*offset = i;
			return dpattr;
		}
	}
	
	return NULL;
}


struct mty_dpattr_t * attr_idx(int32_t index)
{
	struct mtuya_t *mty_p = mty_get();

	if (index >= mty_p->mdps_num) {
		log_error("attr_idx invalid index, max(%d) input(%d)\n", mty_p->mdps_num - 1, index);
		return NULL;
	}
	
	return &mty_p->mdps[index];
}

static int tuya_upload_cache_grow(struct mtuya_t *mty_p, uint32_t new_size)
{
	dp_obj_t *dp_objs;

	if (new_size <= mty_p->cache_num)
		return 0;
	
	if (mty_p->dpup_cache) {
		mdb_free(mty_p->dpup_cache);
	}

	dp_objs = mdb_calloc(new_size, sizeof(dp_obj_t));
	if (!dp_objs) {
		log_error("tuya_upload_cache_grow failed, no memory\n");
		return -1;
	}
	mty_p->cache_num = new_size;
	mty_p->dpup_cache = dp_objs;

	return 0;
}

int tuya_upload_handler(tuya_iot_client_t *client, void *userdata)
{	
	struct mtuya_t *mty_p = mty_get();
	int i = 0, dp_num;
	dp_obj_t *dp_objs;
	struct mty_upload_t *upload_dp;
	struct mty_dpattr_t *dpattr;

retry:
	mdb_lock(mty_p->lock);
	dp_num = mlist_elem_count(&mty_p->upload_list);
	if (dp_num == 0) {
//		log_error("dp report list empty\n");
		mdb_unlock(mty_p->lock);
		return 0;
	}
	
	mdb_unlock(mty_p->lock);

	if (tuya_upload_cache_grow(mty_p, dp_num) < 0) {
		return -1;
	}

	dp_objs = mty_p->dpup_cache;

	for (i = 0; i < dp_num; i++) {
		mdb_lock(mty_p->lock);
		upload_dp = mlist_first_entry(&mty_p->upload_list, struct mty_upload_t, list);
		mlist_del(&upload_dp->list);
		mdb_unlock(mty_p->lock);

		if ((dpattr = dpid2dpattr(upload_dp->dp_id)) == NULL) {
			log_warn("unkown dpid[%d]\n", upload_dp->dp_id);
			continue;
		}

		dp_objs->id = upload_dp->dp_id;
		dp_objs->type = upload_dp->type;
		dp_objs->time_stamp = 0;
		
		switch(upload_dp->type) {
		case PROP_BOOL:
			dp_objs->value.dp_bool = !!upload_dp->value;
			break;
		case PROP_VALUE:
		case PROP_ENUM:
			dp_objs->value.dp_value = upload_dp->value;
			break;
		default:
			log_warn("upload dp_objs addr[%d] unkown type[%d] \n", upload_dp->addr, upload_dp->type);
			break;
		}
		// run upload hook
		if(dpattr->upload_hook) {
			dpattr->upload_hook(dpattr, dp_objs);
		}
			
		mdb_free(upload_dp);
		dp_objs++;
	}

	if (mty_p->tuya_online && strlen(mty_p->devid) != 0)
		tuya_iot_dp_obj_report(client, mty_p->devid, mty_p->dpup_cache, dp_num, 0);

//	log_info("report dp num=[%d]\n", num);
		
	goto retry;
}


void tuya_upload_one_dpobj(int32_t dtype, void *userdata)
{
	struct mtuya_t *mty_p = mty_get();
	uint32_t offset = 0, addr = mdb_get_addr(dtype);
	struct mty_upload_t *upload_dp;
	struct mty_dpattr_t *dpattr;

	//not tuya data
	if ((mdb_get_flag(dtype) & MODULE_TUYA) == 0)
		return;

	// is valid report addr 
	if (addr2dpattr(addr, &offset) == NULL) {
		log_error("tuya_upload_one_dpobj invalid addr\n");
		return;
	}

	//report data
	offset = 0;
	while(1) {
		if ((dpattr = addr2dpattr(addr, &offset)) == NULL) {
			break;
		}	

		mdb_lock(mty_p->lock);
		
		// already in report list, update it
		mlist_for_each_entry(upload_dp, &mty_p->upload_list, struct mty_upload_t, list) {
			if (dpattr->db_addr == upload_dp->addr &&
				dpattr->dp_id == upload_dp->dp_id) {
				upload_dp->value = mdb_get_data(dtype);
				upload_dp->addr = mdb_get_addr(dtype);
				upload_dp->min = mdb_get_min(dtype);
				upload_dp->max = mdb_get_max(dtype);
				continue;
			}
		}
		mdb_unlock(mty_p->lock);

		
		upload_dp = mdb_calloc(1, sizeof(struct mty_upload_t));
		if (!upload_dp) {
			log_error("calloc mty_upload_t memory fail\n");
			return;
		}
		upload_dp->value = mdb_get_data(dtype);
		upload_dp->addr = mdb_get_addr(dtype);
		upload_dp->min = mdb_get_min(dtype);
		upload_dp->max = mdb_get_max(dtype);
		upload_dp->type = dpattr->type;
		upload_dp->dp_id = dpattr->dp_id;

		
		mdb_lock(mty_p->lock);
		mlist_add_tail(&upload_dp->list, &mty_p->upload_list);
		mdb_unlock(mty_p->lock);
		
		// to next same addr dp node
		offset++;
	}

//	log_debug("Report dtype=[%s] addr=[0x%04x] data=[0x%04x]\n",
//		db_name(dtype), dpnew->addr, dpnew->value);
}

static void tuya_register_mdb_dispatch(int32_t dtype, void *userdata)
{
	if ((mdb_get_flag(dtype) & MODULE_TUYA) == 0)
		return;
	
	mdb_register_dispatcher(dtype, tuya_upload_one_dpobj, userdata, MODULE_TUYA);
}

void tuya_upload_all_dpobjs(void)
{
	mdb_foreach_call(tuya_upload_one_dpobj, NULL);
}


int tuya_download_dpobjs(dp_obj_recv_t *dpobj)
{
	uint32_t index = 0;
	struct simple_mnode snode;
	struct mty_dpattr_t *dpattr;

	PR_DEBUG("SOC Rev DP Cmd t1:%d t2:%d CNT:%u", dpobj->cmd_tp, dpobj->dtt_tp, dpobj->dpscnt);
	if (dpobj->devid != NULL) {
		PR_DEBUG("devid.%s", dpobj->devid);
	}
	
	for (index = 0; index < dpobj->dpscnt; index++) {
		dp_obj_t *dp_objs = dpobj->dps + index;
		if ((dpattr = dpid2dpattr(dp_objs->id)) == NULL) {
			log_warn("unkown dpid[%d]\n", dp_objs->id);
			continue;
		}
		// run download hook
		if(dpattr->download_hook) {
			dpattr->download_hook(dpattr, dp_objs);
		}

		PR_DEBUG("idx:%d dpid:%d type:%d ts:%u", index, dp_objs->id, dp_objs->type, dp_objs->time_stamp);
		switch (dp_objs->type) {
		case PROP_BOOL: {
			PR_DEBUG("bool value:%d", dp_objs->value.dp_bool);
			snode.data = (int16_t)dp_objs->value.dp_bool;
			snode.data_len = 4;
			break;
			}
		case PROP_VALUE: {
			PR_DEBUG("int value:%d", dp_objs->value.dp_value);
			snode.data = (int16_t)dp_objs->value.dp_value;
			snode.data_len = 4;
			break;
			}
		case PROP_ENUM: {
			PR_DEBUG("enum value:%u", dp_objs->value.dp_enum);
			snode.data = (int16_t)dp_objs->value.dp_enum;
			snode.data_len = 4;
			break;
			}
		default: {
			PR_ERR("idx:%d dpid:%d type:%d ts:%u is invalid", index, dp_objs->id, dp_objs->type, dp_objs->time_stamp);
			break;
			}
		}
		
		snode.type = DB_WRITE_TYPE_EXTERNAL;
		snode.flag = MODULE_TUYA;
		mdb_update_insert(&snode);
	}
	
	return 0;
}


void tuya_service_init(struct mty_dpattr_t *dpattr, uint32_t num)
{
	struct mtuya_t *mty = mty_get();

	mty->mdps_num = num;
	mty->mdps = dpattr;
	
	mdb_foreach_call(tuya_register_mdb_dispatch, NULL);
}
